package ants;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import cell.Cell;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;


/**
 * Represents the directions of ant neighbor cells.
 */
public enum Direction {
  /** East direction or right. */
  EAST(0, 1, 'e'),
  
  /** North direction, or up. */
  NORTH(-1, 0, 'n'),
  
  /** South direction or down. */
  SOUTH(1, 0, 's'),
  
  /** West direction or left. */
  WEST(0, -1, 'w');
  
  private static final ImmutableMap<Character, Direction> symbolLookup = ImmutableMap.of('n', NORTH, 'e', EAST, 's', SOUTH, 'w', WEST);
  
  
  /**
   * Returns direction associated with specified symbol.
   * 
   * @param symbol
   *          <code>n</code>, <code>e</code>, <code>s</code> or <code>w</code>
   *          character
   * 
   * @return direction associated with specified symbol
   */
  public static Direction fromSymbol(char symbol) {
    return symbolLookup.get(symbol);
  }
  
  
  /**
   * 
   * @param from
   *          the "from" coordinate, either a row or a col. "from" and "to" are
   *          either both rows or both cols.
   * @param to
   *          the "to" coordinate, either a row or a col. "from" and "to" are
   *          either both rows or both cols.
   * @param max
   *          the max value of this axis, either rows or cols.
   * @param defaultDir
   *          the default direction if "from" and "to" are close and "from" <
   *          "to"
   * @return a list of Directions, either empty (if "to" == "from") or with one
   *         element indicating the shortest Direction from "from" to "to".
   */
  public static List<Direction> getDir(int from, int to, int max,
                                       Direction defaultDir) {
    
    final List<Direction> dirs = new ArrayList<>( );
    if (from == to) return dirs;
    
    
    // Each condition
    // (i) Math.abs(to - from) > max / 2
    // (ii) from > to
    // requires the direction to be inverted. If both are true, invert twice,
    // i.e., do nothing. If neither is true also do nothing.
    dirs.add(Math.abs(to - from) > max / 2 != from > to ? defaultDir.invertDirection( )
                                                       : defaultDir);
    return dirs;
  }
  
  
  /**
   * Returns one or two orthogonal directions from one location to the another.
   * 
   * @param from
   *          one location on the game map
   * @param to
   *          another location on the game map
   * 
   * @return orthogonal directions from <code>from</code> to <code>to</code>
   */
  public static List<Direction> getDirections(Cell from, Cell to) {
    final List<Direction> directions = new ArrayList<>( );
    
    directions.addAll(getDir(from.row, to.row, GameState.rows, SOUTH));
    directions.addAll(getDir(from.col, to.col, GameState.cols, EAST));
    
    return directions;
  }
  
  
  public static List<Direction> getRandomDirections( ) {
    return Utils.shuffle(getValuesAsList( ));
  }
  
  
  public static List<Direction> getValuesAsList( ) {
    return Arrays.asList(Direction.values( ));
  }
  
  
  // ////////////////////////// End of static declarations////////
  
  
  private final int  colDelta;
  
  
  private final int  rowDelta;
  
  
  private final char symbol;
  
  
  Direction(int rowDelta, int colDelta, char symbol) {
    this.rowDelta = rowDelta;
    this.colDelta = colDelta;
    this.symbol = symbol;
  }
  
  
  /**
   * Returns symbol associated with this direction.
   * 
   * @return symbol associated with this direction.
   */
  public char getSymbol( ) {
    return symbol;
  }
  
  
  public Direction invertDirection( ) {
    switch (this) {
      case NORTH:
        return Direction.SOUTH;
      case SOUTH:
        return Direction.NORTH;
      case EAST:
        return Direction.WEST;
      case WEST:
        return Direction.EAST;
    }
    return null;
  }
  
  
  /**
   * Returns the location in this direction from the specified location.
   * 
   * @param mapLoc
   *          location on the game map
   * @return location in <code>direction</code> from <cod>mapLoc</code>
   */
  public Cell takeAStep(Cell cell) {
    return takeAStep(cell, GameState.rows, GameState.cols);
  }
  
  
  public static ImmutableList<Direction> cycle( char d){
    return cycle(Direction.fromSymbol(d));
    
  }
  
  public static ImmutableList<Direction> cycle(Direction d){
    List<Direction> dirs = getValuesAsList();
    if(dirs.get(0) == d) return ImmutableList.copyOf(dirs);
   
    final ImmutableList.Builder<Direction> builder = ImmutableList.builder( );
   
    boolean found = false;
    for(Direction newD: dirs){
      if(newD == d) found = true;
      if(found) builder.add(newD);      
    }
    
    List<Direction> l = getValuesAsList();
    for( Direction newD: l){
     if(newD == d) break; 
      builder.add(newD);
    }
  
    return builder.build();
    
    
  }
  
  
  
  /**
   * Returns the location in this direction from the specified location.
   * 
   * @param cell
   *          location on the game map
   * @param rows
   *          the number of rows
   * @param cols
   *          the number of columns
   * @return location in <code>direction</code> from <cod>mapLoc</code>
   */
  private Cell takeAStep(Cell cell, int rows, int cols) {
    return new Cell((cell.row + rowDelta + rows) % rows,
                    (cell.col + colDelta + cols) % cols);
  }
  
  
  /**
   * Returns the symbol character as a String.
   * 
   * @return symbol associated with this direction.
   */
  @Override
  public String toString( ) {
    return String.valueOf(symbol);
  }
  
}
